pub use api::*;
pub use r#type::*;
mod api {
  use std::os::windows::fs::MetadataExt;
  use std::path::PathBuf;
  use windows::Win32::Foundation::ERROR_SUCCESS;
  use windows::Win32::NetworkManagement::NetManagement::{
    NetApiBufferFree, NetUserEnum, USER_INFO_1,
  };
  use windows::Win32::Storage::FileSystem::{FILE_ATTRIBUTE_HIDDEN, INVALID_FILE_ATTRIBUTES};

  use super::DesktopItem;

  pub fn get_desktop_items(
    query_user: Option<String>,
    attr_filter: Option<u32>,
    filters: &Vec<String>,
  ) -> Vec<DesktopItem> {
    let mut items = vec![];

    // Get user list using NetUserEnum
    let mut users = get_system_users();
    users.push("Public".to_string());
    if let Some(query) = query_user {
      if users.iter().any(|v| v == &query) {
        users = vec![query];
      }
    }
    if let Some(system_drive) = std::env::var_os("SystemDrive") {
      for uname in users {
        let desktop_path = PathBuf::from(&system_drive)
          .join("Users")
          .join(&uname)
          .join("Desktop");

        if desktop_path.exists() {
          if let Ok(entries) = std::fs::read_dir(&desktop_path) {
            for entry in entries.flatten() {
              let path = entry.path();
              let attr = if let Ok(meta) = entry.metadata() {
                meta.file_attributes()
              } else {
                INVALID_FILE_ATTRIBUTES
              };
              // 检查文件属性是否匹配筛选条件
              if let Some(filter) = attr_filter {
                if attr == INVALID_FILE_ATTRIBUTES || (attr & filter) != filter {
                  continue;
                }
              }
              if entry
                .file_name()
                .into_string()
                .map(|fname| filters.contains(&fname))
                .unwrap_or_default()
              {
                continue;
              }

              items.push(DesktopItem {
                uname: uname.clone(),
                is_dir: path.is_dir(),
                is_hidden: attr & FILE_ATTRIBUTE_HIDDEN != 0,
                path,
                attribute: attr,
              });
            }
          }
        }
      }
    }

    items
  }

  pub fn get_system_users() -> Vec<String> {
    let mut users = Vec::new();

    unsafe {
      let mut buf = std::ptr::null_mut();
      let mut entries_read = 0u32;
      let mut total_entries = 0u32;
      let mut resume_handle = 0u32;

      let result = NetUserEnum(
        None,
        1,
        0,
        &mut buf,
        u32::MAX,
        &mut entries_read,
        &mut total_entries,
        &mut resume_handle,
      );

      if result == ERROR_SUCCESS {
        let user_info =
          std::slice::from_raw_parts(buf as *const USER_INFO_1, entries_read as usize);
        for info in user_info {
          if let Ok(name) = String::from_utf16(std::slice::from_raw_parts(
            info.usri1_name.0 as *const u16,
            wcslen(info.usri1_name.0),
          )) {
            users.push(name);
          }
        }
        NetApiBufferFree(buf as *mut ::core::ffi::c_void);
      }
    }

    users
  }

  // Helper function to get string length
  unsafe fn wcslen(ptr: *const u16) -> usize {
    let mut len = 0;
    while *ptr.add(len) != 0 {
      len += 1;
    }
    len
  }
}
mod r#type {
  use serde::{Deserialize, Serialize};
  use std::path::PathBuf;
  /// DesktopItems
  #[derive(Debug, Clone, Deserialize, Serialize)]
  pub struct DesktopItem {
    /// 用户名
    pub uname: String,
    /// 路径
    pub path: PathBuf,
    /// 是否目录
    pub is_dir: bool,
    /// 是否隐藏
    pub is_hidden: bool,
    /// 属性ATTRIBUTE
    pub attribute: u32,
  }
}

#[cfg(test)]
mod tests {
  use super::*;
  use std::os::windows::fs::MetadataExt as _;
  use windows::Win32::Storage::FileSystem::*;

  #[test]
  fn test_get_desktop_items_no_filter() {
    let items = get_desktop_items(None, None, &vec![]);
    assert!(!items.is_empty(), "Should find at least some desktop items");
  }

  #[test]
  fn test_get_desktop_items_with_user() {
    if let Some(username) = std::env::var_os("USERNAME") {
      let username = username.to_string_lossy().to_string();
      let items = get_desktop_items(Some(username.clone()), None, &vec![]);
      assert!(!items.is_empty(), "Should find items for current user");
      assert!(
        items.iter().all(move |item| item.uname == username),
        "All items should belong to specified user"
      );
    }
  }

  #[test]
  fn test_get_desktop_items_with_attributes() {
    let test_cases = [
      (FILE_ATTRIBUTE_HIDDEN, "hidden"),
      (FILE_ATTRIBUTE_READONLY, "readonly"),
      (FILE_ATTRIBUTE_SYSTEM, "system"),
      (FILE_ATTRIBUTE_ARCHIVE, "archive"),
      (FILE_ATTRIBUTE_DIRECTORY, "directory"),
    ];

    for (attr, attr_name) in test_cases {
      let items = get_desktop_items(None, Some(attr), &vec![]);
      println!("Testing {}:", attr_name);

      for item in &items {
        if let Ok(metadata) = std::fs::metadata(&item.path) {
          let attrs = metadata.file_attributes();
          println!(
            "- {} (attributes: {:x} 是否隐藏: {} 是否目录: {})",
            item.path.display(),
            attrs,
            item.is_hidden,
            item.is_dir
          );
          assert_ne!(
            attrs & attr,
            0,
            "File {} should have {} attribute",
            item.path.display(),
            attr_name
          );
        }
      }
    }
  }

  #[test]
  fn test_hidden_flag_consistency() {
    let items = get_desktop_items(None, None, &vec![]);
    for item in items {
      if let Ok(metadata) = std::fs::metadata(&item.path) {
        let attrs = metadata.file_attributes();
        let is_actually_hidden = attrs & FILE_ATTRIBUTE_HIDDEN != 0;
        assert_eq!(
          item.is_hidden,
          is_actually_hidden,
          "Hidden flag mismatch for {}",
          item.path.display()
        );
      }
    }
  }

  #[test]
  fn test_get_system_users() {
    let users = get_system_users();
    assert!(!users.is_empty(), "Should find at least one user");

    if let Some(username) = std::env::var_os("USERNAME") {
      let username = username.to_string_lossy().to_string();
      assert!(
        users.contains(&username),
        "Current user should be in the list"
      );
    }
  }
}
